package jwtauth

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/pkg/errors"
	"gpt-api/framework/auther"
	"gpt-api/pkg/util"
)

const defaultKey = "gpt-api"

var (
	defaultOptions = options{
		tokenType:     "Bearer",
		expired:       7200,
		signingMethod: jwt.SigningMethodHS512,
		signingKey:    defaultKey,
		keyfunc: func(t *jwt.Token) (interface{}, error) {
			if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, auther.ErrInvalidToken
			}
			return []byte(defaultKey), nil
		},
	}
)

type options struct {
	signingMethod jwt.SigningMethod
	signingKey    interface{}
	keyfunc       jwt.Keyfunc
	expired       int
	tokenType     string
}

// Option 定义参数项
type Option func(*options)

// SetSigningMethod 设定签名方式
func SetSigningMethod(method jwt.SigningMethod) Option {
	return func(o *options) {
		o.signingMethod = method
	}
}

// SetSigningKey 设定签名key
func SetSigningKey(key interface{}) Option {
	return func(o *options) {
		o.signingKey = key
	}
}

// SetKeyfunc 设定验证key的回调函数
func SetKeyfunc(keyFunc jwt.Keyfunc) Option {
	return func(o *options) {
		o.keyfunc = keyFunc
	}
}

// SetExpired 设定令牌过期时长(单位秒，默认7200)
func SetExpired(expired int) Option {
	return func(o *options) {
		o.expired = expired
	}
}

// New 创建认证实例
func New(store *Storer, opts ...Option) *JWTAuth {
	o := defaultOptions
	for _, opt := range opts {
		opt(&o)
	}

	return &JWTAuth{
		opts:  &o,
		store: store,
	}
}

// JWTAuth jwt认证
type JWTAuth struct {
	opts  *options
	store *Storer
}

func (a *JWTAuth) CheckRefreshToken(refreshToken string) (uint64, error) {
	return a.store.Check(refreshToken)
}

// GenerateToken 生成令牌
func (a *JWTAuth) GenerateToken(info *auther.UserInfo) (auther.TokenInfo, error) {
	now := time.Now()
	expired := time.Duration(a.opts.expired) * time.Second
	expiresAt := now.Add(expired).Unix()
	sub := fmt.Sprintf("%d#", info.UserId)

	token := jwt.NewWithClaims(a.opts.signingMethod, &jwt.StandardClaims{
		IssuedAt:  now.Unix(),
		ExpiresAt: expiresAt,
		NotBefore: now.Unix(),
		Subject:   sub,
	})

	tokenString, err := token.SignedString(a.opts.signingKey)
	if err != nil {
		return nil, err
	}

	refreshToken := util.Md5(tokenString)
	if err := a.callStore(func(store *Storer) error {
		return store.Set(refreshToken, strconv.FormatUint(info.UserId, 10), 4*expired) // 4倍
	}); err != nil {
		return nil, err
	}

	tokenInfo := &tokenInfo{
		ExpiresAt:    expiresAt,
		TokenType:    a.opts.tokenType,
		AccessToken:  tokenString,
		RefreshToken: refreshToken,
	}

	return tokenInfo, nil
}

// DestroyToken 销毁令牌
func (a *JWTAuth) DestroyToken(tokenString string) error {
	claims, err := a.parseToken(tokenString)
	if err != nil {
		return err
	}

	return a.callStore(func(store *Storer) error {
		return store.Set(claims.Subject, tokenString, -1)
	})
}

// 解析令牌
func (a *JWTAuth) parseToken(tokenString string) (*jwt.StandardClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, a.opts.keyfunc)
	if err != nil {
		return nil, errors.Wrap(err, "parse token")
	}
	if !token.Valid {
		return nil, auther.ErrInvalidToken
	}

	return token.Claims.(*jwt.StandardClaims), nil
}

func (a *JWTAuth) callStore(fn func(*Storer) error) error {
	if store := a.store; store != nil {
		return fn(store)
	}
	return nil
}

// ParseUserID 解析用户ID
func (a *JWTAuth) ParseUserID(tokenString string) (*auther.UserInfo, error) {
	claims, err := a.parseToken(tokenString)
	if err != nil {
		return nil, err
	}

	if err := claims.Valid(); err != nil {
		return nil, err
	}
	//if err = a.callStore(func(store *Storer) error {
	//	exists, err := store.Check(sub, tokenString)
	//	if err != nil {
	//		return err
	//	} else if !exists {
	//		return auther.ErrInvalidToken
	//	}
	//	return nil
	//}); err != nil {
	//	return nil, err
	//}

	sub := claims.Subject
	arr := strings.Split(sub, "#")
	if len(arr) != 2 {
		return nil, auther.ErrInvalidToken
	}
	userId, err := strconv.ParseUint(arr[0], 10, 64)
	if err != nil {
		return nil, err
	}

	return &auther.UserInfo{UserId: userId}, nil
}

// Release 释放资源
func (a *JWTAuth) Release() error {
	return a.callStore(func(store *Storer) error {
		return store.Close()
	})
}
